/*
 * $Id: PerforceRepositoryProvider.java 23 2002-09-29 13:59:30Z bpruessmann $
 * 
 * Copyright (c) 2001,2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 *    David Corbin	- added MoveDeleteHook. 
 * 					- modify checkout, uncheckout to IClient, not PerforceUtil
 */
package net.sourceforge.p4eclipse.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sourceforge.p4eclipse.core.api.IResolveResult;
import net.sourceforge.p4eclipse.core.api.IWhereResult;
import net.sourceforge.p4eclipse.core.api.PerforceServerFactory;
import net.sourceforge.p4eclipse.core.api.PerforceUtil;
import net.sourceforge.p4eclipse.core.resources.LocalFile;
import net.sourceforge.p4eclipse.core.resources.LocalFolder;
import net.sourceforge.p4eclipse.core.resources.LocalResource;
import net.sourceforge.p4eclipse.core.resources.PerforceRemoteSyncElement;
import net.sourceforge.p4eclipse.core.resources.RemoteFile;
import net.sourceforge.p4eclipse.core.resources.RemoteFolder;
import net.sourceforge.p4eclipse.core.resources.RemoteResource;
import net.sourceforge.p4eclipse.core.syncinfo.FolderSyncInfo;
import net.sourceforge.p4eclipse.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.p4eclipse.core.util.ChangeSpecification;
import net.sourceforge.p4eclipse.internal.ui.sync.SyncStatus;
import net.sourceforge.p4eclipse.ui.PerforceUIPlugin;
import net.sourceforge.p4eclipse.internal.changes.MoveDeleteHook;
import net.sourceforge.p4eclipse.internal.core.api.Depots;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFileModificationValidator;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.team.IMoveDeleteHook;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.Team;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.sync.IRemoteResource;
import org.eclipse.team.core.sync.IRemoteSyncElement;

/**
  * @version 	$Revision: 23 $
  * @author		<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  * @author		<a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  */
public class PerforceRepositoryProvider extends RepositoryProvider
{
	private IProject project;
	private PerforceFileModificationValidator fileModificationValidator = new PerforceFileModificationValidator(this);
	
	/*
	 * @see RepositoryProvider#getProviderType()
	 */
	public String getID()
	{
		return PerforceUIPlugin.getTypeId();
	}

	/*
	 * @see RepositoryProvider#configureProject()
	 */
	public void configureProject() 
		throws CoreException
	{
	}

	/*
	 * @see IProjectNature#deconfigure()
	 */
	public void deconfigure() 
		throws CoreException
	{
	}

	/*
	 * @see IProjectNature#getProject()
	 */
	public IProject getProject()
	{
		Policy.assertNotNull(project);
		return project;
	}

	/*
	 * @see IProjectNature#setProject(IProject)
	 */
	public void setProject(IProject project)
	{
		Policy.assertNotNull(project);
		this.project = project;		
	}
	
	/**
	  * Addition to ITeamProvider interface.
	  */
	public void add(IResource[] resources,
	                int depth,
	                IProgressMonitor progress)
		throws TeamException
	{
		LocalFile[] filesToAdd = null;
	
		progress.beginTask("", resources.length); //$NON-NLS-1$
		try
		{			
			final List filesToAddList = new ArrayList();
			for (int i = 0, length = resources.length; i < length; i++)
			{
				resources[i].accept(			
					new IResourceVisitor()
					{
						public boolean visit(IResource resource) 
							throws CoreException
						{
							boolean result = true;
							switch (resource.getType())
							{
								case IResource.PROJECT:
								case IResource.FOLDER:
									break;
									
								case IResource.FILE:
									{
										LocalFile localFile = (LocalFile)LocalResource.createFromResource(resource);
										if (!localFile.hasRemote())
											filesToAddList.add(localFile);
									}
									break;			
									
								default:
									Policy.assertTrue(false);
							}
							
							return result;
						}
		
					}, depth, false);					
			}
			
			filesToAdd = (LocalFile[])filesToAddList.toArray(new LocalFile[0]);
			progress.subTask("Perforce: p4 add "+fileSetDescriptor(filesToAdd));
			PerforceUIPlugin.getClient().add(LocalFile.fileNames(filesToAdd));
			progress.done();
		}
		catch(PerforceException pe)
		{
			openDialogOnError(pe);
		}
		catch (CoreException e)
		{
			throw new TeamException(new Status(IStatus.ERROR, PerforceUIPlugin.ID, IStatus.ERROR, "PerforceTeamProvider.add failed.", e));
		}
		finally
		{
			broadcastResourceStateChanges(filesToAdd);
			progress.done();
		}
	}	

	
	public static LocalFile[] getLocalFilesToSubmit(IResource[] resources, int depth) 
		throws CoreException 
	{
		final List filesToSubmitList = new ArrayList();			
		for (int i = 0, length = resources.length; i < length; i++)
		{
			resources[i].accept(
				new IResourceVisitor()
				{
					public boolean visit(IResource resource) 
						throws CoreException
					{
						switch (resource.getType())
						{
							case IResource.PROJECT:
							case IResource.FOLDER:
								break;
		
							case IResource.FILE:
								{
									LocalFile localFile = (LocalFile)LocalResource.createFromResource(resource);
									if (localFile.getSyncInfo().getAction().length() > 0)
										filesToSubmitList.add(localFile);
								}
								break;			
		
							default:
								Policy.assertTrue(false);
						}
		
						return true;
					}
				}, depth, false);
		}
		
		return (LocalFile[])filesToSubmitList.toArray(new LocalFile[0]);
	}	
	
	private String fileSetDescriptor(Object[] fileSet)
	{
		if (fileSet.length == 1)
			return fileSet[0].toString();
		if (fileSet.length == 0)
			return "<none>";
		return "<"+fileSet.length+" files>";
	}	
	private String fileSetDescriptor(List fileSet)
	{
		return fileSetDescriptor(fileSet.toArray());
	}

	public void checkout(IResource[] resources,
	                     int depth,
	                     IProgressMonitor progress)
		throws TeamException
	{
		MultiStatus status = new MultiStatus(PerforceUIPlugin.ID, IStatus.OK, "p4 edit failed.", null);
		LocalFile[] filesToEdit = null;
		
		progress.beginTask("", resources.length); //$NON-NLS-1$	
		try
		{	
			final List filesToEditList = new ArrayList();
			for (int i = 0, length = resources.length; i < length; i++)
			{
				resources[i].accept(			
					new IResourceVisitor()
					{
						public boolean visit(IResource resource) 
							throws CoreException
						{
							boolean result = true;
							switch (resource.getType())
							{
								case IResource.PROJECT:
								case IResource.FOLDER:
									{
										LocalFolder localFolder = (LocalFolder)LocalResource.createFromResource(resource);
										if (!localFolder.hasRemote()) 
											result = false;
									}
									break;
										
								case IResource.FILE:
									{
										LocalFile localFile = (LocalFile)LocalResource.createFromResource(resource);
										if (localFile.hasRemote())
											filesToEditList.add(localFile);
									}
									break;			
									
								default:
									Policy.assertTrue(false);
							}
							
							return result;
						}
		
					}, depth, false);					
			}
	
			filesToEdit = (LocalFile[])filesToEditList.toArray(new LocalFile[0]);
			
			progress.subTask("Perforce: p4 edit "+fileSetDescriptor(filesToEditList));
			
			PerforceUIPlugin.getClient().edit(LocalFile.fileNames(filesToEdit));
			progress.done();
		}	
		catch (CoreException e)
		{
			throw new TeamException(new Status(IStatus.ERROR, PerforceUIPlugin.ID, IStatus.ERROR, "PerforceTeamProvider.edit failed.", e)); //$NON-NLS-1$
		}
		catch (PerforceException pe)
		{
			openDialogOnError(pe);
		}
		finally
		{
			broadcastResourceStateChanges(filesToEdit);
			progress.done();
		}
		
	}

	public void get(IResource[] resources, 
	                int depth, 
	                IProgressMonitor progress)
		throws TeamException
	{
		MultiStatus status = new MultiStatus(PerforceUIPlugin.ID, IStatus.OK, "", null); //$NON-NLS-1$
		progress.beginTask("", resources.length); //$NON-NLS-1$
		
		try
		{
			// we currently only support get of files, so this one is easy
			for (int i = 0; i < resources.length; i++)
			{
				Policy.assertTrue(resources[i].getType() == IResource.FILE);
				
				File file = resources[i].getLocation().toFile();
				ResourceSyncInfo info = PerforceUIPlugin.getSynchronizer().getResourceSync(resources[i]);
				
				progress.subTask("Syncing " + info.getDepotFileName());
				status.merge(PerforceUtil.sync(file, "#head", progress));			
			}
		}
		finally
		{	
			try
			{	
				for (int i = 0, length = resources.length; i < length; i++)
					resources[i].refreshLocal(IResource.DEPTH_ONE, progress);
			}
			catch (CoreException e)
			{
				throw new TeamException(e.getStatus());
			}
			finally
			{				
				broadcastResourceStateChanges(resources);			
				progress.done();
			}
		}

		openDialogOnError(status);		
	}
	public void postActionNotify(IResource[] resources, IProgressMonitor progress)
		throws TeamException
	{
		try
		{	
			for (int i = 0, length = resources.length; i < length; i++)
				resources[i].refreshLocal(IResource.DEPTH_ONE, progress);
		}
		catch (CoreException e)
		{
			throw new TeamException(e.getStatus());
		}
		finally
		{				
			broadcastResourceStateChanges(resources);			
			progress.done();
		}
	}

	public IRemoteSyncElement getRemoteSyncTree(IResource resource, String revision, IProgressMonitor progress)
		throws TeamException 
	{
		int type = resource.getType();
		if (type == IResource.FILE)
		{
			LocalFile localFile = (LocalFile)LocalResource.createFromResource(resource);
			
			IRemoteResource have = new RemoteFile(localFile.getSyncInfo().getDepotFileName(), "#have");
			IRemoteResource head = new RemoteFile(localFile.getSyncInfo().getDepotFileName(), "#head");
			
			return new PerforceRemoteSyncElement(false, localFile, have, head, null);
		}
		else 
		if (type == IResource.FOLDER || type == IResource.PROJECT)
		{
			LocalFolder localFolder = 
				(LocalFolder)LocalResource.createFromResource(resource);

			String[] files = PerforceUtil.sync(localFolder.getAbsolutePath() + "/...", true, new NullProgressMonitor());

			List syncStatus = new ArrayList();

			for (int i = 0, length = files.length; i < length; i++) 
				syncStatus.add(new SyncStatus(files[i], SyncStatus.INCOMING));
			
			IResolveResult[] resolveResults =
				PerforceUIPlugin.getClient().resolve(localFolder.getAbsolutePath() + "/...", true);
			for (int i = 0, length = resolveResults.length; i < length; i++)
				syncStatus.add(new SyncStatus(resolveResults[i].getLocalFilename(), SyncStatus.CONFLICTING));
				
			IRemoteResource have = new RemoteFolder(null, localFolder.getFolderSync(), "#have");
			IRemoteResource head = new RemoteFolder(null, localFolder.getFolderSync(), "#head");
				
			return new PerforceRemoteSyncElement(false, localFolder, have, head, syncStatus.toArray(new SyncStatus[0]));
		}
		
		throw new RuntimeException();
	}

	public RemoteResource getRemoteTree(IResource resource, String revision, IProgressMonitor progress)
		throws TeamException
	{
		RemoteResource result = null;
		if (resource.getType() == IResource.FILE)
		{
			// p4 print file#have seems to be slower than p4 print file#haveRev ...
			if (revision.equals("#have"))
			{
				LocalResource file = LocalResource.createFromResource(resource);
				revision = "#" + file.getSyncInfo().getHaveRev(); 
			}
			
			result = new RemoteFile(PerforceUIPlugin.getClient().where(resource.getLocation().toOSString()).getDepotFilename(), revision);
		}
		else
		{
			FolderSyncInfo[] folderSyncInfo = PerforceUtil.getFolderSyncInfo(PerforceUIPlugin.getClient().where(resource.getLocation().toOSString()).getDepotFilename());
			Policy.assertTrue(folderSyncInfo.length == 1);
			result = new RemoteFolder(null, folderSyncInfo[0], revision);			
		}
		
		return result;
	}

	public boolean hasRemote(IResource resource)
	{
		LocalResource localResource = LocalResource.createFromResource(resource);

		if (resource.getType() == IResource.FILE)
		{
			ResourceSyncInfo info = localResource.getSyncInfo();
			if (!info.isNull())
				return !(info.getHeadAction().length() == 0 && info.getAction().equals(ResourceSyncInfo.ACTION_ADD));
		}
		else
		if (resource.getType() == IResource.FOLDER || resource.getType() == IResource.PROJECT)
		{
			return localResource.hasRemote();
		}

		return false;
	}

	public boolean isCheckedOut(IResource resource)
	{
		LocalResource localResource = LocalResource.createFromResource(resource);
		return localResource.isCheckedOut();			
	}
	
	public void uncheckout(IResource[] resources,
	                       int depth,
	                       IProgressMonitor progress)
		throws TeamException
	{
		LocalFile[] files = new LocalFile[resources.length];

		progress.beginTask("", resources.length); //$NON-NLS-1$
		try
		{
			// we currently only support get of files, so this one is easy
			for (int i = 0, length = resources.length; i < length; i++)
				files[i] = (LocalFile)LocalResource.createFromResource(resources[i]);

			progress.subTask("Perforce: p4 revert "+fileSetDescriptor(files));
			PerforceUIPlugin.getClient().revert(LocalFile.fileNames(files));
			progress.done();

		}
		catch(PerforceException e)
		{
			openDialogOnError(e);
		}
		finally
		{
			try
			{	
				for (int i = 0, length = resources.length; i < length; i++)
					resources[i].refreshLocal(IResource.DEPTH_ONE, progress);
			}
			catch (CoreException e)
			{
				throw new TeamException(e.getStatus());
			}
			finally
			{							
				broadcastResourceStateChanges(resources);			
				progress.done();
			}
		}			
	}

	public void refreshState(IResource[] resources,
	                         int depth,
	                         IProgressMonitor progress)
		throws TeamException
	{
		try
		{
			final List resourcesToRefresh = new ArrayList();
			for (int i = 0, length = resources.length; i < length; i++)
			{
				switch (resources[i].getType())
				{
					case IResource.PROJECT:
					case IResource.FOLDER:
						{
							IContainer folder = (IContainer)resources[i];
							folder.accept(
								new IResourceVisitor()
								{
									/*
									 * @see IResourceVisitor#visit(IResource)
									 */
									public boolean visit(IResource resource) 
									{
										resourcesToRefresh.add(resource);
										return true;
									}
	
								}, depth, false);
						}
						break;
	
					case IResource.FILE:
						resourcesToRefresh.add(resources[i]);
						break;
				}
			}
			
			broadcastResourceStateChanges((IResource[])resourcesToRefresh.toArray(new IResource[0]));
		}
		catch (CoreException e)
		{
			throw new TeamException(new Status(IStatus.ERROR, PerforceUIPlugin.ID, IStatus.ERROR, "PerforceTeamProvider.refreshState failed.", e)); //$NON-NLS-1$
		}
	}

	public void sync(IResource[] resources, 
	                 int depth, 
	                 IProgressMonitor progress)
		throws TeamException
	{

		progress.beginTask("", resources.length); //$NON-NLS-1$
		
		try
		{
			// we currently only support get of files, so this one is easy
			for (int i = 0; i < resources.length; i++)
			{
				/*
				Policy.assertTrue(resources[i].getType() == IResource.FILE);
				
				File file = resources[i].getLocation().toFile();
				ResourceSyncInfo info = PerforceUIPlugin.getSynchronizer().getResourceSyncInfo(resources[i]);
				
				progress.subTask("Syncing " + info.getDepotFileName());
				status.merge(PerforceUtil.sync(file, "#head", progress));			
				*/

				IWhereResult whereResult = PerforceUIPlugin.getClient().where(resources[i].getLocation().toOSString());
				String syncString = whereResult.getClientFilename();
				switch (depth)
				{
					case IResource.DEPTH_ZERO:
						break;
						
					case IResource.DEPTH_ONE:
						syncString += "/*"; //$NON-NLS-1$
						break;
						
					case IResource.DEPTH_INFINITE: //$NON-NLS-1$
						syncString += "/...";
						break;
					
					default:
						Policy.assertTrue(false);
				}
				
				progress.subTask("Syncing " + whereResult.getDepotFilename());
				syncString = syncString + "#head";
				PerforceUIPlugin.getClient().sync(syncString);							
			}
		}
		catch(PerforceException pe)
		{
			openDialogOnError(pe);
		}
		finally
		{	
			try
			{	
				for (int i = 0, length = resources.length; i < length; i++)
					resources[i].refreshLocal(depth, progress);
			}
			catch (CoreException e)
			{
				throw new TeamException(e.getStatus());
			}
			finally
			{	
				refreshState(resources, depth, progress);
				progress.done();
			}
		}
	}
	
	private void openDialogOnError(PerforceException pe)
	{
		IStatus status = pe.getStatus();
		openDialogOnError((MultiStatus)status);
	}
	/**
	  * Translates the provides status to an error dialog. 
	  */
	private void openDialogOnError(final MultiStatus status)
	{
		if (status.getSeverity() != IStatus.ERROR)
			return;;
			
		MultiStatus result = new MultiStatus(PerforceUIPlugin.ID, IStatus.OK, status.getMessage(), null);
		
		IStatus[] children = status.getChildren();
		for (int i = 0, length = children.length; i < length; i++)
		{
			if (children[i].matches(IStatus.ERROR))
				result.merge(children[i]);
		}
		
		if (result.matches(IStatus.ERROR))
		{
			Display.getDefault().syncExec(new Runnable()
			{
				public void run()
				{
					ErrorDialog.openError(null, "Perforce Error", "A Perforce operation failed.", status);
				}
			});
		}
	}	
	
	/**
	  * Broadcast changes to the specified resources.
	  */ 
	private static void broadcastResourceStateChanges(LocalResource[] localResources)
	{
		if (localResources == null)
			return;
			
		IResource[] resources = new IResource[localResources.length];
		for (int i = 0, length = resources.length; i < length; i++)
		{
			resources[i] = localResources[i].toResource();
			try
			{
				switch (resources[i].getType())
				{
					case IResource.PROJECT:
					case IResource.FOLDER:
						IContainer container = (IContainer)resources[i];
						PerforceUIPlugin.getSynchronizer().deleteFolderSync(container);
						break;
						
					case IResource.FILE:
						IContainer parent = resources[i].getParent();
						PerforceUIPlugin.getSynchronizer().deleteFolderSync(parent);
						break;
				}
			
				resources[i].refreshLocal(IResource.DEPTH_ONE, new NullProgressMonitor());
			}
			catch(PerforceException e)
			{
				PerforceUIPlugin.log(e.getStatus());				
			}
			catch(CoreException e) 
			{
				PerforceUIPlugin.log(e.getStatus());
			}
		}
			
		PerforceUIPlugin.getPlugin().broadcastResourceStateChanges(resources);
	}
	
	private static void broadcastResourceStateChanges(IResource[] resources)
	{
		for (int i = 0, length = resources.length; i < length; i++)		
		{
			try
			{
				switch (resources[i].getType())
				{
					case IResource.PROJECT:
					case IResource.FOLDER:
						IContainer container = (IContainer)resources[i];
						PerforceUIPlugin.getSynchronizer().deleteFolderSync(container);
						break;
						
					case IResource.FILE:
						IContainer parent = resources[i].getParent();
						PerforceUIPlugin.getSynchronizer().deleteFolderSync(parent);
						break;
				}
			}
			catch(PerforceException e)
			{
				PerforceUIPlugin.log(e.getStatus());				
			}			
		}

		PerforceUIPlugin.getPlugin().broadcastResourceStateChanges(resources);
	}	
	/*
	 * @see RepositoryProvider#getFileModificationValidator()
	 */
	public IFileModificationValidator getFileModificationValidator()
	{
		return fileModificationValidator;
	}

	public static void setSharing(IProject project, IProgressMonitor monitor)
		throws TeamException
	{
		try 
		{
			if (!project.hasNature(PerforceUIPlugin.NATURE_ID))
			{
				RepositoryProvider.map(project, 
				                       PerforceUIPlugin.NATURE_ID);
			}
		} 
		catch(CoreException e) 
		{
			Policy.assertTrue(false);
		} 
	}
	public IMoveDeleteHook getMoveDeleteHook() 
	{
		return new MoveDeleteHook(PerforceServerFactory.getPerforceServer());
	}
}
